<template>
	<div class="page">
		<!-- 动态背景 -->
		<div class="animated-background"></div>

		<!-- 数字人渲染区域 - 全屏显示 -->
		<div class="wrapper-box" ref="wrapperBox">
		</div>

		<!-- 当出现自动播放受限的情况，需要点击此按钮 -->
		<button v-if="showPlayBtn" class="resume-button" @click="resumePlay">
			点击恢复音视频播放
		</button>

		<!-- 控制按钮 - 简洁悬浮式 -->
		<div class="control-buttons">
			<button @click="oneClickStart" class="start-btn">启动</button>
			<button @click="oneClickStop" class="stop-btn">停止</button>
		</div>

		<!-- 自定义字幕区域 -->
		<div class="subtitle-container" v-if="showSubtitle">
			<div class="subtitle-box">
				{{ currentSubtitleText }}
			</div>
		</div>

		<!-- 底部悬浮输入框 -->
		<div class="floating-input">
			<uv-input v-model="driveText" placeholder="和小灵聊聊天吧..." type="text" border="none"
				placeholderClass="input-placeholder"
				placeholderStyle="color: rgba(255, 255, 255, 0.7); font-size: 18px;" fontSize="18px" color="#ffffff"
				:customStyle="{ padding: '10px 0' }" />
			<view @click="askModel" :disabled="isAvatarSpeaking || isLoading" class="send-btn"
				:class="{ disabled: isAvatarSpeaking || isLoading }">
				<icon class="iconfont bs-send" v-if="!isLoading && !isAvatarSpeaking"></icon>
				<icon class="loading-icon" v-else></icon>
			</view>
			<view @click="askModel" :disabled="isAvatarSpeaking || isLoading" class="talk"
				:class="{ disabled: isAvatarSpeaking || isLoading }">
				<icon class="iconfont bs-talk"></icon>
			</view>
		</div>
	</div>
</template>

<script lang="ts" setup>
import { ref, reactive, onMounted, onUnmounted, markRaw, nextTick } from 'vue'
import AvatarPlatform, { SDKEvents, PlayerEvents } from '../../sdk/3.0.0.1002/avatar-sdk-web_3.1.2.1002/index.js'
import OpenAI from "openai"
import functions from '@/api/function.js'
import { functionDefinitions } from '@/api/functionDefinitions.js'
import openaiService from '@/api/openaiService.js' // 导入OpenAI服务

// 收集所有已经创建的实例，用来在需要时一键销毁
const avatarInstances: any[] = []

// 字幕相关状态
const showSubtitle = ref(false)
const currentSubtitleText = ref('')
const subtitleQueue = ref<{ reqId: string, text: string }[]>([])
const isSubtitleActive = ref(false)
const subtitleTimeout = ref<number | null>(null)
const currentReqId = ref<string | null>(null)

// 数字人说话状态
const isAvatarSpeaking = ref(false)

// 空闲超时设置
const IDLE_TIMEOUT = 3 * 60 * 1000 // 3分钟空闲自动销毁
let idleTimer: number | null = null // 空闲计时器

// 消息历史记录相关
const chatHistory = ref<Array<{ role: string, content: string }>>([])
const MAX_HISTORY_TURNS = 3 // 保存最近3轮对话

// 添加一个用于记录已经显示过的工具提示消息的集合
const shownToolMessages = ref(new Set())

// 声明全局变量来跟踪每轮对话中显示过的提示消息
const currentDialogToolMessages = ref(new Set())

// 保存聊天记录到本地缓存
function saveChatHistory() {
	try {
		uni.setStorageSync('chatHistory', JSON.stringify(chatHistory.value))
		console.log('[saveChatHistory] 聊天记录已保存到本地缓存')
	} catch (e) {
		console.error('[saveChatHistory] 保存聊天记录失败:', e)
	}
}

// 从本地缓存加载聊天记录
function loadChatHistory() {
	try {
		const history = uni.getStorageSync('chatHistory')
		if (history) {
			chatHistory.value = JSON.parse(history)
			console.log('[loadChatHistory] 从本地缓存加载聊天记录成功')
		}
	} catch (e) {
		console.error('[loadChatHistory] 加载聊天记录失败:', e)
		// 如果加载失败，初始化为空数组
		chatHistory.value = []
	}
}

// 添加消息到历史记录
function addMessageToHistory(role: string, content: string) {
	// 添加新消息
	chatHistory.value.push({ role, content })

	// 如果超过最大轮次限制，移除最旧的消息
	const messagesPerTurn = 2 // 一轮包含用户消息和助手消息
	if (chatHistory.value.length > MAX_HISTORY_TURNS * messagesPerTurn) {
		// 移除最旧的一轮对话(两条消息)
		chatHistory.value = chatHistory.value.slice(messagesPerTurn)
	}

	// 保存到本地缓存
	saveChatHistory()
}

// API信息
const apiInfo = reactive({
	serverUrl: 'wss://avatar.cn-huadong-1.xf-yun.com/v1/interact',
	appId: '3f06eacc',
	apiKey: '6849334313edb242b179adf1e8b6ccd4',
	apiSecret: 'OGIwNTRlNmVkODgyYzc0Zjc5MjBlNzYy',
	sceneId: '137731881486651392'
})

// SDK 初始化附加选项
const initOptions = reactive({
	useInlinePlayer: true
})

// 全局参数设置
const globalParams = reactive({
	stream: {
		protocol: 'xrtc',
		alpha: 1,
		fps: 25
	},
	avatar: {
		avatar_id: '130033001',
		width: 720,
		height: 1280
	},
	tts: {
		vcn: 'x4_lingxiaoqi_oral',  // 声音资源ID
		speed: 50,         // 语速，取值范围0~100，默认50
		pitch: 50,         // 语调，取值范围0~100，默认50
		volume: 100,       // 音量，取值范围0~100，默认100
		audio: {
			sample_rate: 16000  // 采样率
		}
	},
	subtitle: {
		subtitle: 1,        // 是否开启字幕 0-关闭 1-开启
		// font_color: '#FFFFFF' // 字体颜色
	},
	air: {
		air: 1,
		add_nonsemantic: 1,
	}
})

// 用于播放状态
const showPlayBtn = ref(false)

// 包裹数字人DOM的ref
const wrapperBox = ref<HTMLDivElement | null>(null)

// 当前活跃实例
let avatarPlatformRef: any = null

// 模型相关
const isLoading = ref(false)
const abortController = ref<AbortController | null>(null)
const driveText = ref('')
const models = ref([
	{ label: '基础模型', value: 'qwen-plus' },
])
const selectedModel = ref('qwen-plus')

// OpenAI配置
const openaiConfigs = {
	// 基础模型使用OpenAI配置
	'qwen-plus': new OpenAI({
		baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
		apiKey: 'sk-b06fd3a49a724abbac6d4452aee90b37',
		dangerouslyAllowBrowser: true
	}),
};

// 获取当前模型对应的OpenAI实例
function getOpenAIInstance() {
	return openaiConfigs[selectedModel.value] || openaiConfigs['qwen-plus'];
}

// 重置空闲计时器
function resetIdleTimer() {
	// 清除现有计时器
	if (idleTimer !== null) {
		clearTimeout(idleTimer)
		idleTimer = null
	}

	// 设置新计时器
	idleTimer = setTimeout(() => {
		console.log('[idleTimer] 检测到3分钟无交互，自动销毁数字人')
		// 确保数字人已停止说话
		if (!isAvatarSpeaking.value) {
			uni.showToast({ title: '检测到长时间无交互，自动停止数字人', icon: 'none' })
			oneClickStop() // 自动停止数字人
		} else {
			// 如果数字人还在说话，延迟再检查
			console.log('[idleTimer] 数字人正在说话，延迟销毁')
			resetIdleTimer() // 重置定时器，等待说话结束
		}
	}, IDLE_TIMEOUT)

	console.log('[idleTimer] 重置空闲计时器')
}

// 一键启动函数 - 按顺序执行：初始化SDK，绑定SDK事件，绑定Player事件，startAvatar
async function oneClickStart() {
	try {
		// 重置字幕状态
		clearSubtitleQueue()

		// 1. 初始化SDK
		console.log('[oneClickStart] 正在初始化SDK...')
		const newInstance = markRaw(new AvatarPlatform({ ...initOptions }))
		avatarInstances.push(newInstance)
		avatarPlatformRef = newInstance

		// 2. 设置API信息和全局参数
		avatarPlatformRef.setApiInfo({
			appId: apiInfo.appId,
			apiKey: apiInfo.apiKey,
			apiSecret: apiInfo.apiSecret,
			sceneId: apiInfo.sceneId,
			serverUrl: apiInfo.serverUrl
		})
		avatarPlatformRef.setGlobalParams(globalParams)

		// 3. 绑定SDK事件
		console.log('[oneClickStart] 正在绑定SDK事件...')
		bindSDKEvents()

		// 4. 绑定Player事件
		console.log('[oneClickStart] 正在绑定Player事件...')
		bindPlayerEvents()

		// 5. 启动Avatar
		console.log('[oneClickStart] 正在启动Avatar...')
		if (!wrapperBox.value) {
			throw new Error('渲染容器不存在')
		}

		await avatarPlatformRef.start({ wrapper: wrapperBox.value })
		console.log('[oneClickStart] 成功启动数字人')
		uni.showToast({ title: '成功启动数字人', icon: 'success' })

		// 启动空闲计时器
		resetIdleTimer()

		// 延迟一段时间后自动发送问候语
		setTimeout(() => {
			sendGreeting()
		}, 1000) // 等待1秒，确保数字人已经准备好
	} catch (err: any) {
		console.error('[oneClickStart] 启动失败:', err)
		uni.showToast({ title: '启动失败: ' + (err.message || '未知错误'), icon: 'none' })
	}
}

// 发送问候语
function sendGreeting() {
	if (!avatarPlatformRef) {
		console.warn('[sendGreeting] 数字人未启动，无法发送问候语')
		return
	}

	console.log('[sendGreeting] 发送问候语')
	const greeting = '你好，小灵'

	// 添加到历史记录
	addMessageToHistory('user', greeting)

	// 设置数字人正在说话状态
	isAvatarSpeaking.value = true

	// 使用追加模式替代打断模式发送问候语
	avatarPlatformRef
		.writeText('你好，我是小灵，山东师范大学实验室智能助手，有什么可以帮助你的吗？', {
			avatar_dispatch: {
				interactive_mode: 0 // 使用追加模式替代打断模式
			}
		})
		.then((reqId: string) => {
			console.log('[sendGreeting] 发送问候语成功，reqId=', reqId)

			// 添加到字幕队列，带上reqId
			addToSubtitleQueue(reqId, '你好，我是小灵，山东师范大学实验室智能助手，有什么可以帮助你的吗？')

			// 添加到历史记录
			addMessageToHistory('assistant', '你好，我是小灵，山东师范大学实验室智能助手，有什么可以帮助你的吗？')
		})
		.catch((err: any) => {
			console.error('[sendGreeting] 发送问候语失败:', err)
			// 发送失败时重置说话状态
			isAvatarSpeaking.value = false
		})
}

// 一键停止函数 - 按顺序执行：stopAvatar，destroy
async function oneClickStop() {
	try {
		// 清理字幕状态
		clearSubtitleQueue()

		// 清除空闲计时器
		if (idleTimer !== null) {
			clearTimeout(idleTimer)
			idleTimer = null
		}

		if (!avatarPlatformRef) {
			uni.showToast({ title: '未启动数字人', icon: 'none' })
			return
		}

		// 1. 停止Avatar
		console.log('[oneClickStop] 正在停止Avatar...')
		avatarPlatformRef.stop?.()

		// 2. 销毁实例
		console.log('[oneClickStop] 正在销毁实例...')
		avatarPlatformRef.destroy?.()

		// 移除实例引用
		const idx = avatarInstances.indexOf(avatarPlatformRef)
		if (idx !== -1) {
			avatarInstances.splice(idx, 1)
		}
		avatarPlatformRef = null

		uni.showToast({ title: '成功停止数字人', icon: 'success' })
	} catch (err: any) {
		console.error('[oneClickStop] 停止失败:', err)
		uni.showToast({ title: '停止失败: ' + (err.message || '未知错误'), icon: 'none' })
	}
}

// 绑定SDK事件
function bindSDKEvents() {
	if (!avatarPlatformRef) return

	avatarPlatformRef.removeAllListeners?.()
	avatarPlatformRef
		.on(SDKEvents.connected, (initResp: any) => {
			// 获取到拉流地址
			console.log('[SDKEvents.connected]', initResp)
		})
		.on(SDKEvents.stream_start, () => {
			// 引擎侧 首帧 推流开始 （注意：不代表端侧已经开始有画面！）
			console.log('[SDKEvents.stream_start]')
		})
		.on(SDKEvents.disconnected, (err: any) => {
			console.log('[SDKEvents.disconnected] err=', err)
			if (err) {
				// 因为异常而导致的断开！此处可以进行提示通知等
				console.error('ws link disconnected because of Error')
				console.error(err.code, err.message, err.name, err.stack)
			}
		})
		.on(SDKEvents.subtitle_info, (subtitleData: any) => {
			console.log('[SDKEvents.subtitle_info]', subtitleData)
		})
		.on(SDKEvents.asr, (asrData: any) => {
			// 监听到语音的识别结果（asr）
			console.log('[SDKEvents.asr]', asrData)
		})
		.on(SDKEvents.nlp, (nlpData: any) => {
			// 监听到大模型语义理解结果（nlp）
			console.log('[SDKEvents.nlp]', nlpData)
		})
		.on(SDKEvents.frame_start, (frameData: any) => {
			//监听到开始说话的推流首帧
			console.log('[SDKEvents.frame_start]', frameData)

			// 设置数字人正在说话状态
			isAvatarSpeaking.value = true

			// 清除结束对话的超时
			clearSubtitleTimeout()

			// 提取本次要说的文本内容并显示在字幕中
			if (frameData && frameData.request_id) {
				// 打印当前队列状态，帮助调试
				console.log('[字幕系统] frame_start时队列状态:', JSON.stringify(subtitleQueue.value.map(item =>
					({ reqId: item.reqId.substring(0, 8), text: item.text.substring(0, 20) + (item.text.length > 20 ? '...' : '') })
				)))

				// 记录当前正在处理的reqId
				currentReqId.value = frameData.request_id

				// 根据reqId查找匹配的字幕内容
				activateSubtitleByReqId(frameData.request_id)
			}
		})
		.on(SDKEvents.tts_duration, (sessionData: any) => {
			// 监听TTS的时长信息
			console.log('[SDKEvents.tts_duration]', sessionData)
		})
		.on(SDKEvents.frame_stop, (frameData: any) => {
			//监听到结束说话的末尾帧
			console.log('[SDKEvents.frame_stop]', frameData)

			if (frameData && frameData.request_id) {
				// 打印当前队列状态，帮助调试
				console.log('[字幕系统] frame_stop时队列状态:', JSON.stringify(subtitleQueue.value.map(item =>
					({ reqId: item.reqId.substring(0, 8), text: item.text.substring(0, 20) + (item.text.length > 20 ? '...' : '') })
				)))

				// 标记该reqId对应的字幕已完成
				completeSubtitleByReqId(frameData.request_id)
			}

			// 设置可能的对话结束检测
			handleDialogueEnd()
		})
		.on(SDKEvents.action_start, (actionData: any) => {
			//监听到开始动作的推流首帧
			console.log('[SDKEvents.action_start]', actionData)
		})
		.on(SDKEvents.action_stop, (actionData: any) => {
			//监听到结束动作的末尾帧
			console.log('[SDKEvents.action_stop]', actionData)
		})
		.on(SDKEvents.error, (error: any) => {
			//监听到错误信息
			console.log('[SDKEvents.error]', error)
		})
}

// 绑定Player事件
function bindPlayerEvents() {
	if (!avatarPlatformRef) return

	const player = avatarPlatformRef.player || avatarPlatformRef.createPlayer()
	player?.removeAllListeners?.()
	player
		.on(PlayerEvents.error, (err: any) => {
			console.log('[PlayerEvents.error]', err)
		})
		.on(PlayerEvents.play, () => {
			console.log('[PlayerEvents.play]')
		})
		.on(PlayerEvents.playing, () => {
			console.log('[PlayerEvents.playing]')
		})
		.on(PlayerEvents.playNotAllowed, () => {
			console.log('[PlayerEvents.playNotAllowed]')
			showPlayBtn.value = true
		})
}

// 直接文本驱动
function writeText() {
	if (!avatarPlatformRef) {
		uni.showToast({ title: '请先启动数字人', icon: 'none' })
		return
	}
	if (!driveText.value.trim()) {
		uni.showToast({ title: '请输入文本', icon: 'none' })
		return
	}

	// 设置数字人正在说话状态
	isAvatarSpeaking.value = true

	avatarPlatformRef
		.writeText(driveText.value, {
			avatar_dispatch: {
				interactive_mode: 0  // 使用追加模式替代打断模式
			}
		})
		.then((reqId: string) => {
			console.log('[writeText] success, reqId=', reqId)
			uni.showToast({ title: '文本发送成功', icon: 'success' })

			// 添加到字幕队列，带上reqId
			addToSubtitleQueue(reqId, driveText.value)
		})
		.catch((err: any) => {
			console.error('[writeText] failed', err)
			uni.showToast({ title: '发送失败', icon: 'none' })
			// 发送失败时重置说话状态
			isAvatarSpeaking.value = false
		})
}

// 向模型发送请求
async function askModel() {
	if (!avatarPlatformRef) {
		uni.showToast({ title: '请先启动数字人', icon: 'none' })
		return
	}

	if (!driveText.value.trim()) {
		uni.showToast({ title: '请输入文本', icon: 'none' })
		return
	}

	if (isLoading.value || isAvatarSpeaking.value) {
		uni.showToast({ title: '请等待数字人回应完成', icon: 'none' })
		return
	}

	// 重置空闲计时器
	resetIdleTimer()

	// 存储发送的文本内容
	const textToSend = driveText.value

	// 清空输入框
	driveText.value = ''

	isLoading.value = true
	abortController.value = new AbortController()

	// 将用户消息添加到历史记录
	addMessageToHistory('user', textToSend)

	try {
		// 准备消息列表，包含历史消息
		const messages = [
			...chatHistory.value
		]

		console.log('[askModel] 发送消息包含历史记录:', messages)

		// 清空字幕队列，防止历史字幕残留
		clearSubtitleQueue()
		
		// 重置当前对话中显示过的工具消息集合
		currentDialogToolMessages.value.clear()
		
		// 重置全局已显示的工具提示消息
		shownToolMessages.value.clear()

		// 用于收集完整回复
		let fullResponseText = ''
		let hasToolCall = false
		let isResponseCollecting = false

		// 使用openaiService.streamChatCompletion处理对话
		await openaiService.streamChatCompletion({
			messages: messages,
			model: selectedModel.value,
			signal: abortController.value.signal,
			systemPrompt: '你叫小灵，是山东师范大学大学实验室智能助手。请使用自然、口语化的语言回答，不要使用markdown、列表或其他格式化文本。像正常对话一样用简单直接的语言表达，避免使用"以下是"、"总结"等书面语言风格。保持温暖亲切的语气，就像在面对面交谈一样。',

			// 普通文本响应处理 - 只收集文本，不立即发送
			onChunk: async (content, chunk) => {
				// 收集文本到完整响应
				fullResponseText += content

				// 只在有字符内容且未在工具调用期间时，标记为正在收集响应
				if (content && !hasToolCall) {
					isResponseCollecting = true
				}
			},

			// 工具调用开始前的处理
			onToolCallStarted: async () => {
				// 先发送已收集的文本（如果有）
				if (fullResponseText.trim()) {
					const reqId = await avatarPlatformRef.writeText(fullResponseText.trim(), {
						avatar_dispatch: {
							interactive_mode: 0 // 使用追加模式替代打断模式
						}
					})

					// 添加到字幕队列，带上reqId
					addToSubtitleQueue(reqId, fullResponseText.trim())
					
					// 重置收集的文本
					fullResponseText = ''
				}
				
				hasToolCall = true
				isResponseCollecting = false
				
				// 延迟一小段时间后再发送工具处理消息
				await new Promise(resolve => setTimeout(resolve, 100))
				
				// 通知用户正在处理查询，但只在当前对话中第一次显示这个消息
				const processingMessage = '我需要查询一些信息，请稍等...'
				if (!currentDialogToolMessages.value.has(processingMessage)) {
					currentDialogToolMessages.value.add(processingMessage)
					
					const processingReqId = await avatarPlatformRef.writeText(processingMessage, {
						avatar_dispatch: {
							interactive_mode: 0 // 使用追加模式
						},
						append: true
					})
					
					// 添加到字幕队列，带上reqId
					addToSubtitleQueue(processingReqId, processingMessage)
					
					// 确保字幕已经被处理，打印日志
					console.log('[字幕系统] 工具调用开始，添加处理消息，reqId:', processingReqId)
				}
			},
			
			// 每次工具调用时的处理
			onToolCall: async (deltaToolCalls, toolCalls) => {
				// 查找最新的工具调用
				const validCalls = toolCalls.filter(call => call !== null && call.function && call.function.name)
				if (validCalls.length > 0) {
					const latestCall = validCalls[validCalls.length - 1]
					
					// 根据函数名称生成友好的消息
					const actionMessage = getFriendlyActionMessage(latestCall.function.name)
					
					// 检查当前对话中是否已经显示过这个消息，避免重复显示
					if (!currentDialogToolMessages.value.has(actionMessage)) {
						// 将消息添加到当前对话的跟踪集合
						currentDialogToolMessages.value.add(actionMessage)
						
						// 发送给数字人
						const actionReqId = await avatarPlatformRef.writeText(actionMessage, {
							avatar_dispatch: {
								interactive_mode: 0 // 使用追加模式
							},
							append: true
						})

						// 添加到字幕队列，带上reqId
						addToSubtitleQueue(actionReqId, actionMessage)
						
						// 记录已显示的消息
						console.log('[字幕系统] 显示工具调用消息:', actionMessage)
					} else {
						console.log('[字幕系统] 跳过重复的工具调用消息:', actionMessage)
					}
				}
			},
			
			// 工具调用结束时的处理
			onToolCallEnd: async (toolCalls, results) => {
				// 通知用户工具调用已完成，但只在当前对话中第一次显示这个消息
				const completionMessage = '信息查询完成，正在为你生成回答...'
				if (!currentDialogToolMessages.value.has(completionMessage)) {
					// 将消息添加到当前对话的跟踪集合
					currentDialogToolMessages.value.add(completionMessage)
					
					const completionReqId = await avatarPlatformRef.writeText(completionMessage, {
						avatar_dispatch: {
							interactive_mode: 0 // 使用追加模式
						},
						append: true
					})

					// 添加到字幕队列，带上reqId
					addToSubtitleQueue(completionReqId, completionMessage)
				}
			},

			// 工具调用完全结束后的处理
			onToolCallFinished: async (results) => {
				console.log('[askModel] 所有工具调用已完成，即将生成最终回答')
				hasToolCall = false
				fullResponseText = '' // 重置收集的文本，准备接收工具调用后的新回复
			},

			// 错误处理
			onError: async (error) => {
				console.error('[askModel] 请求失败:', error)

				if (error.name !== 'AbortError') {
					uni.showToast({ title: '请求失败: ' + (error.message || '未知错误'), icon: 'none' })

					// 将错误回复添加到历史记录
					addMessageToHistory('assistant', `请求失败: ${error.message || '未知错误'}`)

					// 向数字人发送错误消息
					const errorReqId = await avatarPlatformRef.writeText(`很抱歉，出现了错误：${error.message || '未知错误'}。请稍后再试。`, {
						avatar_dispatch: {
							interactive_mode: 0 // 使用追加模式替代打断模式
						},
						append: true
					})

					// 添加到字幕队列，带上reqId
					addToSubtitleQueue(errorReqId, `很抱歉，出现了错误：${error.message || '未知错误'}。请稍后再试。`)
				}

				isLoading.value = false
			},

			// 对话完成处理
			onFinish: async (result) => {
				console.log('[askModel] 对话完成:', result)

				// 将完整响应添加到历史记录
				if (result && result.content) {
					addMessageToHistory('assistant', result.content)

					// 如果在工具调用过程中收集了响应，或者没有通过onChunk累积任何文本
					// 则使用result.content作为最终响应发送
					if (!isResponseCollecting || fullResponseText.length === 0) {
						fullResponseText = result.content;
					}
				}

				// 发送最终完整的回复（无论是通过onChunk累积的还是result.content）
				if (fullResponseText.trim()) {
					console.log('[askModel] 发送完整回复:', fullResponseText.trim())

					// 对完整回复进行自然句子分割
					await sendTextInNaturalChunks(fullResponseText.trim())
				}

				isLoading.value = false
			}
		})

	} catch (error) {
		console.error('[askModel] 顶层错误处理:', error)
		isLoading.value = false
		abortController.value = null
	}
}

// 根据函数名称获取友好消息，避免重复相同的提示
function getFriendlyActionMessage(functionName) {
	let actionMessage = '';
	
	switch (functionName) {
		case 'queryLabs':
			actionMessage = '正在为你查询实验室信息...'
			break
		case 'queryEquipment':
			actionMessage = '正在查询设备信息...'
			break
		case 'queryReservations':
			actionMessage = '正在查询预约记录...'
			break
		case 'addReservation':
			actionMessage = '正在处理预约请求...'
			break
		case 'getUserInfo':
			// 用户信息查询不显示提示，避免打断对话流程
			// 因为用户信息查询通常很快，不需要特别提示
			return null
		case 'reservationProcess':
			actionMessage = '正在处理预约流程...'
			break
		case 'getTodayDate':
			// 日期查询不显示提示，通常很快不需要特别提示
			return null
		default:
			actionMessage = `正在${functionName}...`
	}
	
	return actionMessage;
}

// 按自然句子分割发送文本
async function sendTextInNaturalChunks(text) {
	if (!text || !text.trim() || !avatarPlatformRef) {
		console.warn('无有效文本或数字人未初始化')
		return
	}

	console.log('[sendTextInNaturalChunks] 开始处理文本:', text)

	// 句子分隔符
	const sentenceSeparators = /([。！？.!?])/g

	// 将文本分割成句子
	let sentences = text.split(sentenceSeparators)

	// 重组句子（将分隔符放回句子中）
	let chunks = []
	for (let i = 0; i < sentences.length; i += 2) {
		let sentence = sentences[i]
		let separator = sentences[i + 1] || ''
		if (sentence || separator) {
			chunks.push((sentence + separator).trim())
		}
	}

	// 如果没有成功分句，则整体作为一句
	if (chunks.length === 0) {
		chunks = [text]
	}

	// 合并太短的句子（少于10个字符的句子与下一句合并）
	let mergedChunks = []
	let currentChunk = ''

	for (let i = 0; i < chunks.length; i++) {
		if (chunks[i].length < 10 && i < chunks.length - 1) {
			currentChunk += chunks[i]
		} else {
			currentChunk += chunks[i]
			if (currentChunk.trim()) {
				mergedChunks.push(currentChunk.trim())
			}
			currentChunk = ''
		}
	}

	// 如果还有剩余的短句，添加到结果中
	if (currentChunk.trim()) {
		mergedChunks.push(currentChunk.trim())
	}

	console.log('[sendTextInNaturalChunks] 分割后的句子:', mergedChunks)

	// 逐句发送
	let isFirstChunk = true
	for (const chunk of mergedChunks) {
		try {
			// 创建发送选项
			const options = {
				append: !isFirstChunk, // 如果不是第一块，则使用追加模式
				avatar_dispatch: {
					interactive_mode: 0 // 全部使用追加模式，取消打断模式
				}
			}

			// 发送句子给数字人
			const reqId = await avatarPlatformRef.writeText(chunk, options)
			console.log('[sendTextInNaturalChunks] 发送句子:', chunk, '请求ID:', reqId)

			// 添加到字幕队列，带上reqId
			addToSubtitleQueue(reqId, chunk)

			// 更新首次标记
			if (isFirstChunk) {
				isFirstChunk = false
			}

			// 短暂延迟，让数字人有时间处理
			await new Promise(resolve => setTimeout(resolve, 20))
		} catch (error) {
			console.error('[sendTextInNaturalChunks] 发送文本失败:', error)
		}
	}
}

// 恢复播放
function resumePlay() {
	if (!avatarPlatformRef?.player) return

	avatarPlatformRef.player.resume()
	showPlayBtn.value = false
}

// 生命周期钩子
onMounted(() => {
	// 清除聊天历史记录
	chatHistory.value = []
	saveChatHistory()
	console.log('[onMounted] 清除聊天历史记录')

	// 页面加载时自动启动数字人
	nextTick(() => {
		oneClickStart()
	})
})

// 页面销毁时自动停止数字人
onUnmounted(() => {
	console.log('[onUnmounted] 页面销毁，自动停止数字人')

	// 清理字幕状态
	clearSubtitleQueue()

	// 重置数字人说话状态
	isAvatarSpeaking.value = false

	if (abortController.value) {
		abortController.value.abort()
	}
	// 停止所有实例
	avatarInstances.forEach(instance => {
		try {
			instance.stop?.()
			instance.destroy?.()
		} catch (err) {
			console.error('[onUnmounted] 实例销毁失败:', err)
		}
	})
	// 清空实例列表
	avatarInstances.length = 0
	avatarPlatformRef = null
})

// 分段发送文本给数字人
async function sendTextInChunks(text) {
	if (!text || !text.trim() || !avatarPlatformRef) {
		console.warn('无有效文本或数字人未初始化')
		return
	}

	// 定义中英文标点符号正则表达式（增强版）
	const punctuationRegex = /[。，、？！；：""''（）【】《》…·.,:;!?\-—\[\](){}"""'']/

	// 将文本根据标点符号分割成段落
	let tempText = ''
	let isFirstChunk = true

	// 逐字符处理文本
	for (let i = 0; i < text.length; i++) {
		const char = text[i]
		tempText += char

		// 检查当前字符是否是标点符号
		if (punctuationRegex.test(char) || tempText.length > 50) {
			// 如果有足够的文本积累，发送一个段落
			if (tempText.trim()) {
				try {
					// 第一次发送打断，后续追加
					const options = {
						append: true,
						avatar_dispatch: {
							interactive_mode: isFirstChunk ? 1 : 0
						}
					}

					const reqId = await avatarPlatformRef.writeText(tempText.trim(), options)
					console.log('[sendTextInChunks] 发送文本段落:', tempText.trim(), '请求ID:', reqId)

					// 添加到字幕队列，带上reqId
					addToSubtitleQueue(reqId, tempText.trim())

					// 更新首次标记
					if (isFirstChunk) {
						isFirstChunk = false
					}

					// 重置临时文本
					tempText = ''

					// 添加短暂延迟，确保数字人处理完前一段文本
					await new Promise(resolve => setTimeout(resolve, 30))
				} catch (error) {
					console.error('[sendTextInChunks] 发送文本段落失败:', error)
				}
			}
		}
	}

	// 处理剩余的文本（如果有）
	if (tempText.trim()) {
		try {
			const reqId = await avatarPlatformRef.writeText(tempText.trim(), {
				append: true,
				avatar_dispatch: {
					interactive_mode: isFirstChunk ? 1 : 0
				}
			})
			console.log('[sendTextInChunks] 发送剩余文本:', tempText.trim(), '请求ID:', reqId)

			// 添加到字幕队列，带上reqId
			addToSubtitleQueue(reqId, tempText.trim())
		} catch (error) {
			console.error('[sendTextInChunks] 发送剩余文本失败:', error)
		}
	}
}

// 清除字幕超时
function clearSubtitleTimeout() {
	if (subtitleTimeout.value !== null) {
		clearTimeout(subtitleTimeout.value)
		subtitleTimeout.value = null
	}
}

// 添加文本到字幕队列（带reqId）
function addToSubtitleQueue(reqId, text) {
	if (!text || !text.trim() || !reqId) return

	// 将文本添加到队列
	subtitleQueue.value.push({
		reqId: reqId,
		text: text.trim()
	})

	console.log('[字幕系统] 添加字幕到队列，reqId:', reqId, '文本:', text.trim())
}

// 根据请求ID激活字幕
function activateSubtitleByReqId(reqId) {
	if (!reqId) return

	// 查找匹配的字幕
	const matchingSubtitle = subtitleQueue.value.find(item => item.reqId === reqId)

	if (matchingSubtitle) {
		// 显示匹配的字幕
		showSubtitle.value = true
		currentSubtitleText.value = matchingSubtitle.text
		isSubtitleActive.value = true
		currentReqId.value = reqId

		console.log('[字幕系统] 显示字幕，reqId:', reqId, '文本:', matchingSubtitle.text)
	} else {
		console.warn('[字幕系统] 未找到匹配的字幕，reqId:', reqId)
	}
}

// 根据请求ID完成字幕显示
function completeSubtitleByReqId(reqId) {
	if (!reqId) return

	console.log('[字幕系统] 尝试完成字幕显示，reqId:', reqId, '当前reqId:', currentReqId.value)

	// 检查队列中是否存在该reqId的字幕
	const index = subtitleQueue.value.findIndex(item => item.reqId === reqId);
	if (index === -1) {
		console.log('[字幕系统] reqId不在队列中，忽略:', reqId);
		return;
	}

	// 从队列中移除该reqId的字幕
	subtitleQueue.value.splice(index, 1);

	// 如果移除的是当前显示的字幕，需要处理下一个字幕
	if (reqId === currentReqId.value) {
		console.log('[字幕系统] 完成当前显示的字幕，reqId:', reqId);

		// 从frame_start事件我们知道下一个要显示的字幕的reqId已经在currentReqId中
		// 所以这里不需要立即调用processNextSubtitle，等待下一个frame_start事件

		// 如果队列为空，则隐藏字幕
		if (subtitleQueue.value.length === 0) {
			hideSubtitle();
		}
	} else {
		console.log('[字幕系统] 移除队列中的非当前字幕，reqId:', reqId);
	}
}

// 处理下一个字幕
function processNextSubtitle() {
	// 如果队列为空，结束字幕显示
	if (subtitleQueue.value.length === 0) {
		hideSubtitle()
		return
	}

	// 从队列中获取第一个要显示的字幕项
	// 注意：此时可能存在队列中的项会被跳过，因为frame_start事件已经触发了对应的字幕显示
	// 所以我们不应该永远显示队列中的第一项

	// 检查是否有新的frame_start事件触发的显示请求
	// 如果currentReqId已经设置，且对应的字幕在队列中，优先显示那个
	let nextSubtitleIndex = -1;

	if (currentReqId.value) {
		// 查找当前正在显示的reqId在队列中的位置
		nextSubtitleIndex = subtitleQueue.value.findIndex(item => item.reqId === currentReqId.value);
	}

	// 如果没找到当前显示的reqId或者没有当前显示的reqId，则显示队列中的第一项
	if (nextSubtitleIndex === -1) {
		nextSubtitleIndex = 0;
	}

	const nextSubtitle = subtitleQueue.value[nextSubtitleIndex];

	// 标记字幕为活跃状态
	isSubtitleActive.value = true
	currentReqId.value = nextSubtitle.reqId

	// 显示字幕文本
	showSubtitle.value = true
	currentSubtitleText.value = nextSubtitle.text

	console.log('[字幕系统] 显示字幕，reqId:', nextSubtitle.reqId, '文本:', nextSubtitle.text)
}

// 隐藏字幕
function hideSubtitle() {
	clearSubtitleTimeout()

	// 先添加淡出动画类
	const container = document.querySelector('.subtitle-container')
	if (container) {
		container.classList.add('hiding')

		// 等待动画完成后再隐藏
		setTimeout(() => {
			isSubtitleActive.value = false
			showSubtitle.value = false
			currentSubtitleText.value = ''
			currentReqId.value = null

			// 移除动画类，为下次显示做准备
			if (container) {
				container.classList.remove('hiding')
			}

			console.log('[字幕系统] 隐藏字幕')
		}, 300) // 动画持续时间为300ms
	} else {
		// 如果DOM元素不存在，直接隐藏
		isSubtitleActive.value = false
		showSubtitle.value = false
		currentSubtitleText.value = ''
		currentReqId.value = null
		console.log('[字幕系统] 隐藏字幕')
	}
}

// 清空字幕队列
function clearSubtitleQueue() {
	subtitleQueue.value = []
	hideSubtitle()
}

// 处理对话结束 (当frame_stop事件后2秒内没有新的frame_start触发)
function handleDialogueEnd() {
	clearSubtitleTimeout()

	// 设置超时，如果2秒内没有新的frame_start，则认为对话结束
	subtitleTimeout.value = setTimeout(() => {
		// 清理字幕队列
		clearSubtitleQueue()

		// 重置数字人说话状态
		isAvatarSpeaking.value = false

		// 重置空闲计时器
		resetIdleTimer()
	}, 2000)
}
</script>

<style scoped lang="scss">
@import "/dam_uni_frame/styles/dam_theme_common.scss";
@import "/static/css/iconfont.css";

.page {
	width: 100%;
	height: 100vh;
	position: relative;
	box-sizing: border-box;
	background-color: #000;
	display: flex;
	flex-direction: column;
	overflow: hidden;
}

/* 动态背景 */
.animated-background {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	background: linear-gradient(125deg, #3498db, #8e44ad, #3498db, #9b59b6, #2980b9);
	background-size: 500% 500%;
	animation: gradientBG 18s ease infinite;
	opacity: 0.8;
	z-index: 0;
}

@keyframes gradientBG {
	0% {
		background-position: 0% 50%;
	}

	25% {
		background-position: 50% 100%;
	}

	50% {
		background-position: 100% 50%;
	}

	75% {
		background-position: 50% 0%;
	}

	100% {
		background-position: 0% 50%;
	}
}

/* 数字人渲染区域 - 全屏 */
.wrapper-box {
	width: 100%;
	height: 100%;
	position: absolute;
	top: 0;
	left: 0;
	z-index: 1;
	display: flex;
	align-items: center;
	justify-content: center;
}

/* 控制按钮 - 悬浮式 */
.control-buttons {
	position: absolute;
	top: 20px;
	right: 20px;
	display: flex;
	gap: 10px;
	z-index: 10;

	button {
		width: 70px;
		height: 36px;
		font-size: 14px;
		font-weight: bold;
		border: none;
		border-radius: 20px;
		color: white;
		backdrop-filter: blur(10px);
		box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
	}

	.start-btn {
		background-color: rgba(76, 175, 80, 0.8);

		&:active {
			background-color: rgba(61, 139, 64, 0.9);
		}
	}

	.stop-btn {
		background-color: rgba(244, 67, 54, 0.8);

		&:active {
			background-color: rgba(211, 47, 47, 0.9);
		}
	}
}

/* 底部悬浮输入框 */
.floating-input {
	position: fixed;
	bottom: 20px;
	left: 50%;
	transform: translateX(-50%);
	width: 85%;
	display: flex;
	align-items: center;
	background-color: rgba(255, 255, 255, 0.15);
	backdrop-filter: blur(10px);
	border-radius: 30px;
	padding: 5px 10px 2px 15px;
	box-shadow: 0 4px 20px rgba(255, 255, 255, 0.3);
	z-index: 100;


	.send-btn {
		padding: 5px;
		background: linear-gradient(135deg, #2196F3, #085ddd);
		color: white;
		height: 40px;
		width: 40px;
		border-radius: 50%;
		display: flex;
		justify-content: center;
		align-items: center;
		box-shadow: 0 3px 10px rgba(33, 150, 243, 0.5);
		transition: all 0.3s ease;


		&:active {
			box-shadow: 0 5px 15px rgba(13, 71, 161, 0.7);
		}

		&:disabled,
		&.disabled {
			background: rgba(150, 150, 150, 0.7);
			box-shadow: none;
			opacity: 0.7;
			pointer-events: none;
		}

		icon {
			font-size: 25px;
		}

		.iconfont {
			width: 25px;
			height: 25px;
		}
	}

	.talk {
		margin-left: 8px;
		padding: 5px;
		background: linear-gradient(135deg, #c081d5, #a604dc);
		color: white;
		height: 40px;
		width: 40px;
		border-radius: 50%;
		display: flex;
		justify-content: center;
		align-items: center;
		box-shadow: 0 3px 10px #b948df;
		transition: all 0.3s ease;


		&:active {
			box-shadow: 0 5px 15px #b948df;
		}

		&:disabled,
		&.disabled {
			background: rgba(150, 150, 150, 0.7);
			box-shadow: none;
			opacity: 0.7;
			pointer-events: none;
		}

		icon {
			font-size: 25px;
		}

		.iconfont {
			width: 25px;
			height: 25px;
		}

	}

	.iconfont {
		display: block;
		width: 18px;
		height: 18px;
		color: white;
		font-size: 20px;
	}

	.loading-icon {
		display: block;
		width: 20px;
		height: 20px;
		border: 2px solid #fff;
		border-radius: 50%;
		border-top-color: transparent;
		animation: spin 0.8s linear infinite;
	}
}

@keyframes spin {
	to {
		transform: rotate(360deg);
	}
}

.resume-button {
	position: fixed;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	background: rgba(255, 51, 85, 0.9);
	color: #fff;
	padding: 15px 20px;
	border: none;
	font-size: 16px;
	border-radius: 8px;
	box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
	backdrop-filter: blur(5px);
	z-index: 1000;
}

/* 自定义字幕样式 */
.subtitle-container {
	position: absolute;
	bottom: 90px;
	/* 位于输入框上方 */
	left: 50%;
	transform: translateX(-50%);
	width: 90%;
	max-width: 600px;
	z-index: 10;
	text-align: center;
}

.subtitle-box {
	background-color: rgba(0, 0, 0, 0.75);
	color: white;
	padding: 14px 22px;
	border-radius: 12px;
	font-size: 18px;
	font-weight: 500;
	line-height: 1.6;
	box-shadow: 0 8px 20px rgba(0, 0, 0, 0.6);
	backdrop-filter: blur(8px);
	animation: fadeIn 0.3s ease;
	word-break: break-word;
	max-height: 200px;
	overflow-y: auto;
	text-align: center;
	transition: all 0.3s ease;
	border: 1px solid rgba(255, 255, 255, 0.12);
	letter-spacing: 0.5px;
}

.subtitle-container.hiding .subtitle-box {
	opacity: 0;
	transform: translateY(15px);
	filter: blur(2px);
}

@keyframes fadeIn {
	from {
		opacity: 0;
		transform: translateY(15px);
		filter: blur(2px);
	}

	to {
		opacity: 1;
		transform: translateY(0);
		filter: blur(0);
	}
}
</style>